package com.sc.common.util;

import com.sc.common.context.DefaultBusinessContext;
import com.sc.common.exception.BusinessException;
import com.sc.common.util.cache.SpringRedisTools;
import com.sc.common.lock.RedisDistributeLock;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 *
 * Function:
 * Reason:可在分布式并发环境下使用
 * Date:2018/8/6
 * @author wust
 */
public class CodeGenerator {

    private static final String CODE_TYPE_IMPORT_EXPORT_CODE = "importExportCode";
    private static final String CODE_TYPE_COMPANY_CODE = "companyCode";
    private static final String CODE_TYPE_DEPARTMENT_CODE = "departmentCode";
    private static final String CODE_TYPE_PROJECT_CODE = "projectCode";
    private static final String CODE_TYPE_ROLE_CODE = "roleCode";
    private static final String CODE_TYPE_BUILDING_CODE = "buildingCode";
    private static final String CODE_TYPE_WORK_ORDER_CODE = "workorderCode";

    private CodeGenerator() {
    }


    /**
     * 建筑编码
     * @return
     */
    public static String genBuildingCode(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        int codeLength = 7;

        String batchNo = "";

        String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());

        String key = CODE_TYPE_BUILDING_CODE + dateStr;

        long newValue = getNewValueByKey(key,1,1,TimeUnit.DAYS,"Building Code");
        batchNo = "BLG" + dateStr + StringUtils.leftPad(newValue + "",codeLength,"0");

        try {
            boolean result = redisDistributeLock.lock(batchNo);
            while (!result) {
                newValue = getNewValueByKey(key, 1, 1, TimeUnit.DAYS, "Building Code");
                batchNo = "BLG" + dateStr + StringUtils.leftPad(newValue + "", codeLength, "0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(batchNo);
        }
        return batchNo;
    }


    /**
     * 导入导出批次号
     * @return
     */
    public static String genImportExportCode(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        int codeLength = 6;

        String batchNo = "";

        String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());

        String key = CODE_TYPE_IMPORT_EXPORT_CODE + dateStr;

        long newValue = getNewValueByKey(key,1,1,TimeUnit.DAYS,"Import Export BatchNo");
        batchNo = dateStr + StringUtils.leftPad(newValue + "",codeLength,"0");

        try {
            boolean result = redisDistributeLock.lock(batchNo);
            while (!result) {
                newValue = getNewValueByKey(key,1,1,TimeUnit.DAYS,"Import Export BatchNo");
                batchNo = dateStr + StringUtils.leftPad(newValue + "",codeLength,"0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(batchNo);
        }

        return batchNo;
    }



    public static String genCompanyCode(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        int codeLength = 4;

        String code = "";

        String key =  CODE_TYPE_COMPANY_CODE;

        long newValue = getNewValueByKey(key,1,0,null,"Company Code");
        code = "GS" + StringUtils.leftPad(newValue + "",codeLength,"0");

        try {
            boolean result = redisDistributeLock.lock(code);
            while (!result) {
                newValue = getNewValueByKey(key,1,0,null,"Company Code");
                code = "GS" + StringUtils.leftPad(newValue + "",codeLength,"0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(code);
        }

        return code;
    }

    public static String genDetartmentCode(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        int codeLength = 4;

        String code = "";

        String key =  CODE_TYPE_DEPARTMENT_CODE;

        long newValue = getNewValueByKey(key,1,0,null,"Department Code");
        code = StringUtils.leftPad(newValue + "",codeLength,"0");
        try {
            boolean result = redisDistributeLock.lock(code);
            while (!result) {
                newValue = getNewValueByKey(key,1,0,null,"Department Code");
                code = StringUtils.leftPad(newValue + "",codeLength,"0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(code);
        }
        return "BM" + code;
    }

    public static String genProjectCode(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        int codeLength = 4;

        String code = "";

        String key =  CODE_TYPE_PROJECT_CODE;

        long newValue = getNewValueByKey(key,1,0,null,"Project Code");
        code = "XM" + StringUtils.leftPad(newValue + "",codeLength,"0");
        try {
            boolean result = redisDistributeLock.lock(code);
            while (!result) {
                newValue = getNewValueByKey(key,1,0,null,"Project Code");
                code = "XM" + StringUtils.leftPad(newValue + "",codeLength,"0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(code);
        }
        return code;
    }


    public static String genRoleCode(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        int codeLength = 4;

        String code = "";

        String key =  CODE_TYPE_ROLE_CODE;

        long newValue = getNewValueByKey(key,1,0,null,"Role Code");
        code = "JS" + StringUtils.leftPad(newValue + "",codeLength,"0");
        try {
            boolean result = redisDistributeLock.lock(code);
            while (!result) {
                newValue = getNewValueByKey(key,1,0,null,"Role Code");
                code = "JS" + StringUtils.leftPad(newValue + "",codeLength,"0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(code);
        }
        return code;
    }


    public static String genWorkOrderNumberByCompany(){
        RedisDistributeLock redisDistributeLock = SpringContextHolder.getBean("redisDistributeLock");
        long start = System.currentTimeMillis();

        DefaultBusinessContext ctx = DefaultBusinessContext.getContext();

        int codeLength = 4;

        String key =  CODE_TYPE_WORK_ORDER_CODE + new DateTime().toString("yyyy-MM-dd");

        long newValue = getNewValueByKey(key,1,1,TimeUnit.DAYS,"Work Order Code");

        String agentIdString = ctx.getAgentId().toString();
        String projectIdString = ctx.getProjectId().toString();
        String code = "WO" + agentIdString.substring(agentIdString.length()-4) + projectIdString.substring(projectIdString.length()-4) + (new DateTime().toString("yyyyMMdd")) + StringUtils.leftPad(newValue + "",codeLength,"0");
        try {
            boolean result = redisDistributeLock.lock(code);
            while (!result) {
                newValue = getNewValueByKey(key,1,1,TimeUnit.DAYS,"Work Order Code");
                code = "WO" + agentIdString.substring(agentIdString.length()-4) + projectIdString.substring(projectIdString.length()-4) + (new DateTime().toString("yyyyMMdd")) + StringUtils.leftPad(newValue + "",codeLength,"0");
                long end = System.currentTimeMillis();
                if ((end - start) / 1000 > 20) { // 20秒超时设置，防止活锁
                    throw new BusinessException("生成编码失败");
                }
            }
        }finally {
            redisDistributeLock.unLock(code);
        }
        return code;
    }


    /**
     * 原子自增方法，失败时默认重试5次
     * @param key redis的key
     * @param value 自增值
     * @param desc 用于打印日志的描述
     * @return 自增value值后的值
     */
    private static long getNewValueByKey(String key,long value,long timeout,TimeUnit unit,String desc){
        SpringRedisTools springRedisTools = SpringContextHolder.getBean("springRedisTools");

        long newValue = 0;

        int tryCount = 5;

        do {
            newValue = springRedisTools.incrementForLong(key, value, timeout, unit);
            if(newValue < 1 && tryCount > 0){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
                tryCount --;
            }else{
                break;
            }
        }while (true);

        if(newValue < 1){
            throw new BusinessException("生成["+desc+"]失败");
        }

        return newValue;
    }
}
